package org.smartboot.compare.array;

import org.smartboot.compare.Accessor;
import org.smartboot.compare.Option;
import org.smartboot.compare.constants.CompareConstants;

import java.lang.reflect.Array;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;

/**
 * @author qinluo
 * @date 2024-06-08 18:14:47
 * @since 1.1.0
 */
public class ArrayAccessor implements Accessor {

    private static final Map<Class<?>, BiFunction<Object, Integer, Object>> fastElementReaderMap = new ConcurrentHashMap<>(16);

    static {
        fastElementReaderMap.put(int.class, (v1, index) -> ((int[])v1)[index]);
        fastElementReaderMap.put(Integer.class, (v1, index) -> ((Integer[])v1)[index]);
        fastElementReaderMap.put(short.class, (v1, index) -> ((short[])v1)[index]);
        fastElementReaderMap.put(Short.class, (v1, index) -> ((Short[])v1)[index]);
        fastElementReaderMap.put(double.class, (v1, index) -> ((double[])v1)[index]);
        fastElementReaderMap.put(Double.class, (v1, index) -> ((Double[])v1)[index]);
        fastElementReaderMap.put(float.class, (v1, index) -> ((float[])v1)[index]);
        fastElementReaderMap.put(Float.class, (v1, index) -> ((Float[])v1)[index]);
        fastElementReaderMap.put(byte.class, (v1, index) -> ((byte[])v1)[index]);
        fastElementReaderMap.put(Byte.class, (v1, index) -> ((Byte[])v1)[index]);
        fastElementReaderMap.put(long.class, (v1, index) -> ((long[])v1)[index]);
        fastElementReaderMap.put(Long.class, (v1, index) -> ((Long[])v1)[index]);
        fastElementReaderMap.put(boolean.class, (v1, index) -> ((boolean[])v1)[index]);
        fastElementReaderMap.put(Boolean.class, (v1, index) -> ((Boolean[])v1)[index]);
        fastElementReaderMap.put(char.class, (v1, index) -> ((char[])v1)[index]);
        fastElementReaderMap.put(Character.class, (v1, index) -> ((Character[])v1)[index]);
        fastElementReaderMap.put(String.class, (v1, index) -> ((String[])v1)[index]);
    }

    public static void registerElementReader(Class<?> elementType, BiFunction<Object, Integer, Object> func) {
        fastElementReaderMap.put(elementType, func);
    }

    private final Object array;
    private final int length;
    private final int type;
    private final Class<?> elementType;
    private final BiFunction<Object, Integer, Object> elementReader;

    public ArrayAccessor(Object arrayObject, int type, Class<?> elementType) {
        this.array = arrayObject;
        this.length = ArrayHelper.getLength(this.array);
        this.type = type;
        this.elementType = elementType;
        if (fastElementReaderMap.containsKey(elementType)) {
            elementReader = fastElementReaderMap.get(elementType);
        } else {
            elementReader = (v1, index) -> Array.get(array, index);
        }
    }

    @Override
    public int getLength() {
        return length;
    }

    @Override
    public Object elementAt(int index) {
        return elementReader.apply(array, index);
    }

    @Override
    public Object getSource() {
        return array;
    }

    @Override
    public boolean supportOption(Option option) {
        if (option == Option.TRANS_AS_SET) {
            return type != CompareConstants.OBJECT;
        } else if(option == Option.BEAUTIFUL_ARRAY_RESULT) {
            return type != CompareConstants.OBJECT;
        }

        return true;
    }

    @Override
    public int getSourceType() {
        return type;
    }

    @Override
    public Class<?> elementType() {
        return elementType;
    }

    @Override
    public Set<Object> convert2Set() {
        Set<Object> hashSet = new HashSet<>(32);
        for (int i = 0; i < length; i++) {
            hashSet.add(elementReader.apply(array, i));
        }
        return hashSet;
    }
}
